#pragma once


#include "Color.hpp"
#include <vector>
#include <string>
#include <fstream>


class Framebuffer
{
public:
    Framebuffer(int width, int height): _width(width), _height(height) { _data.resize((std::size_t)_width * _height); }

    void clear(Color color) { for (auto& it : _data) it = color; }
    void clear() { clear(Color()); }

    int width() const { return _width; }
    int height() const { return _height; }

    void  set(int row, int col, Color color) { _data[(std::size_t)row * _width + col] = color; }
    Color get(int row, int col) { return _data[(std::size_t)row * _width + col]; }
    const Color& get(int row, int col) const { return _data[(std::size_t)row * _width + col]; }

    void toPPM(const std::string& filepath)
    {
        std::ofstream ofile(filepath, std::ios::out);
        ofile << "P3\n";
        ofile << _width << ' ' << _height << "\n";
        ofile << "255\n";

        for (int row = 0; row < _height; row++)
        {
            for (int col = 0; col < _width; col++)
            {
                const Color& color = get(row, col);
                ofile << static_cast<int>(color.red() * 255.999f) << ' ';
                ofile << static_cast<int>(color.green() * 255.999f) << ' ';
                ofile << static_cast<int>(color.blue() * 255.999f) << ' ';
            }
            ofile << "\n";
        }
    }

    void toBMP(const std::string& filepath)
    {
        std::ofstream ofile(filepath, std::ios::binary | std::ios::out);

        // calculate row size with paddings
        int rowSize = (_width * 3 + 3) / 4 * 4;

        // bmp file header
        ofile.write("BM", 2);                                                   // signature
        ofile.write(intToLittleEndian(54 + 3 * rowSize * _height).data(), 4);   // file size
        ofile.write(intToLittleEndian(0).data(), 4);                            // reserved
        ofile.write(intToLittleEndian(54).data(), 4);                           // data offset

        // bmp info header
        ofile.write(intToLittleEndian(40).data(), 4);                           // info header size
        ofile.write(intToLittleEndian(_width).data(), 4);                       // width
        ofile.write(intToLittleEndian(_height).data(), 4);                      // height
        ofile.write(intToLittleEndian(1).data(), 2);                            // planes
        ofile.write(intToLittleEndian(24).data(), 2);                           // bits per pixel
        ofile.write(intToLittleEndian(0).data(), 4);                            // compression
        ofile.write(intToLittleEndian(0).data(), 4);                            // image size
        ofile.write(intToLittleEndian(0).data(), 4);                            // horizontal pixels per meter
        ofile.write(intToLittleEndian(0).data(), 4);                            // vertical pixels per meter
        ofile.write(intToLittleEndian(0).data(), 4);                            // number of colors used
        ofile.write(intToLittleEndian(0).data(), 4);                            // number of important colors

        // image data
        for (int row = _height - 1; row >= 0; row--)
        {
            for (int col = 0; col < _width; col++)
            {
                // gamma correct for gamma 2.0
                ofile.put(static_cast<char>(sqrt(get(row, col).blue())  * 255.999f));
                ofile.put(static_cast<char>(sqrt(get(row, col).green()) * 255.999f));
                ofile.put(static_cast<char>(sqrt(get(row, col).red())   * 255.999f));
            }
            for (int i = _width * 3 + 1; i < rowSize; i++)
                ofile.put(static_cast<char>(0));
        }
    }

private:
    std::vector<char> intToLittleEndian(int n)
    {
        std::vector<char> str(4);
        str[0] = static_cast<char>(n & 0xFF);
        str[1] = static_cast<char>((n >> 8) & 0xFF);
        str[2] = static_cast<char>((n >> 16) & 0xFF);
        str[3] = static_cast<char>((n >> 24) & 0xFF);
        return str;
    }

private:
    std::vector<Color>      _data;
    int                     _width;
    int                     _height;
};
